import prisma from '../utils/db';
import bcrypt from 'bcrypt';
import jwt from 'jsonwebtoken';
import { AppError } from '../utils/errors';
import crypto from 'crypto';
import { MailService } from './mail.service';

export class AuthService {
  private mailService = new MailService();

  private async getOrCreateOrganizationForEmail(email: string) {
    const domain = email.split('@')[1];
    if (!domain) return null;
    
    const name = domain.toUpperCase();
    if (!name) return null;

    let org = await prisma.organization.findFirst({
      where: { name }
    });

    if (!org) {
      org = await prisma.organization.create({
        data: { name }
      });
    }
    return org.id;
  }

  public async register(data: any) {
    const existing = await prisma.user.findUnique({ where: { email: data.email } });
    if (existing) throw new AppError('Email already in use', 400);

    let orgId = data.organizationId || null;
    if (!orgId && data.role !== 'ADMIN') {
      orgId = await this.getOrCreateOrganizationForEmail(data.email);
    }

    const passwordHash = await bcrypt.hash(data.password, 10);
    const user = await prisma.user.create({
      data: {
        email: data.email,
        passwordHash,
        role: data.role || 'PARTNER_USER',
        organizationId: orgId,
        onboardingStatus: data.role === 'ADMIN' ? 'APPROVED' : 'PENDING_ONBOARDING'
      }
    });
    
    return this.getUserById(user.id);
  }

  public async invitePartner(email: string, options: { organizationId?: string, partnerGroup?: string, assignedNdaId?: string, assignedMsaId?: string, sharedAssetIds?: string[], mfaEnabled?: boolean, showEcosystemTab?: boolean } = {}) {
    const existing = await prisma.user.findUnique({ where: { email } });
    if (existing) throw new AppError('Email already in use', 400);

    let orgId = options.organizationId || null;
    if (!orgId) {
      orgId = await this.getOrCreateOrganizationForEmail(email);
    }

    const inviteToken = crypto.randomBytes(32).toString('hex');
    const inviteTokenExp = new Date(Date.now() + 24 * 60 * 60 * 1000); // 24 hours

    const hasAssets = options.sharedAssetIds && options.sharedAssetIds.length > 0;
    const onboardingStatus = hasAssets ? 'PENDING_ONBOARDING' : 'PENDING_ASSETS';

    let assignedNdaId: string | null = null;
    if (options.assignedNdaId === 'NONE') {
      assignedNdaId = null;
    } else if (options.assignedNdaId === undefined || options.assignedNdaId === '') {
      // Resolve active default NDA
      const activeNda = await prisma.legalDocument.findFirst({
        where: { type: 'NDA', isActive: true },
        select: { id: true }
      });
      assignedNdaId = activeNda?.id || null;
    } else {
      assignedNdaId = options.assignedNdaId;
    }

    let assignedMsaId: string | null = null;
    if (options.assignedMsaId === 'NONE') {
      assignedMsaId = null;
    } else if (options.assignedMsaId === undefined || options.assignedMsaId === '') {
      // Resolve active default MSA
      const activeMsa = await prisma.legalDocument.findFirst({
        where: { type: 'MSA', isActive: true },
        select: { id: true }
      });
      assignedMsaId = activeMsa?.id || null;
    } else {
      assignedMsaId = options.assignedMsaId;
    }

    const user = await prisma.user.create({
      data: {
        email,
        passwordHash: '', // Set on accept
        role: 'PARTNER_USER',
        organizationId: orgId,
        inviteToken,
        inviteTokenExp,
        onboardingStatus,
        mfaEnabled: options.mfaEnabled !== undefined ? options.mfaEnabled : true,
        showEcosystemTab: options.showEcosystemTab !== undefined ? options.showEcosystemTab : true,
        partnerGroup: options.partnerGroup || null,
        assignedNdaId,
        assignedMsaId,
      }
    });

    if (hasAssets && orgId) {
      await prisma.sharedAsset.createMany({
        data: options.sharedAssetIds!.map(assetId => ({
          assetId,
          organizationId: orgId!,
          userId: user.id,
        })),
        skipDuplicates: true
      });
    }

    let emailSent = false;
    let emailError: string | undefined = undefined;

    // Trigger SMTP mail if assets are assigned
    if (onboardingStatus === 'PENDING_ONBOARDING') {
      try {
        await this.mailService.sendInviteEmail(email, inviteToken);
        emailSent = true;
      } catch (err) {
        console.error(`[SMTP ERROR] Non-fatal invitation mail failure: ${(err as Error).message}`);
        emailError = (err as Error).message;
      }
    }

    return { inviteToken, emailSent, emailError };
  }

  public async updatePartner(partnerId: string, options: { partnerGroup?: string | null, assignedNdaId?: string | null, assignedMsaId?: string | null, sharedAssetIds?: string[], mfaEnabled?: boolean, showEcosystemTab?: boolean }) {
    const user = await prisma.user.findUnique({
      where: { id: partnerId }
    });
    if (!user) throw new AppError('Partner not found', 404);

    const oldStatus = user.onboardingStatus;
    const orgId = user.organizationId;

    let assignedNdaId = user.assignedNdaId;
    if (options.assignedNdaId !== undefined) {
      if (options.assignedNdaId === 'NONE') {
        assignedNdaId = null;
      } else if (options.assignedNdaId === '') {
        const activeNda = await prisma.legalDocument.findFirst({
          where: { type: 'NDA', isActive: true },
          select: { id: true }
        });
        assignedNdaId = activeNda?.id || null;
      } else {
        assignedNdaId = options.assignedNdaId;
      }
    }

    let assignedMsaId = user.assignedMsaId;
    if (options.assignedMsaId !== undefined) {
      if (options.assignedMsaId === 'NONE') {
        assignedMsaId = null;
      } else if (options.assignedMsaId === '') {
        const activeMsa = await prisma.legalDocument.findFirst({
          where: { type: 'MSA', isActive: true },
          select: { id: true }
        });
        assignedMsaId = activeMsa?.id || null;
      } else {
        assignedMsaId = options.assignedMsaId;
      }
    }

    // Update basic fields
    await prisma.user.update({
      where: { id: partnerId },
      data: {
        partnerGroup: options.partnerGroup !== undefined ? options.partnerGroup : user.partnerGroup,
        assignedNdaId,
        assignedMsaId,
        mfaEnabled: options.mfaEnabled !== undefined ? options.mfaEnabled : user.mfaEnabled,
        showEcosystemTab: options.showEcosystemTab !== undefined ? options.showEcosystemTab : user.showEcosystemTab,
      }
    });

    // Update shared assets if provided
    if (options.sharedAssetIds !== undefined) {
      // Clear existing shared assets for this user (both user-specific and org-wide)
      if (orgId) {
        await prisma.sharedAsset.deleteMany({
          where: {
            OR: [
              { userId: partnerId },
              { organizationId: orgId, userId: null }
            ]
          }
        });
      } else {
        await prisma.sharedAsset.deleteMany({
          where: { userId: partnerId }
        });
      }

      // Create new shared assets if any
      if (options.sharedAssetIds.length > 0 && orgId) {
        await prisma.sharedAsset.createMany({
          data: options.sharedAssetIds.map(assetId => ({
            assetId,
            organizationId: orgId,
            userId: partnerId,
          })),
          skipDuplicates: true
        });
      }

      // Check transition from PENDING_ASSETS to PENDING_ONBOARDING
      if (oldStatus === 'PENDING_ASSETS' && options.sharedAssetIds.length > 0) {
        let inviteToken = user.inviteToken;
        let inviteTokenExp = user.inviteTokenExp;

        if (!inviteToken) {
          inviteToken = crypto.randomBytes(32).toString('hex');
          inviteTokenExp = new Date(Date.now() + 24 * 60 * 60 * 1000);
        }

        await prisma.user.update({
          where: { id: partnerId },
          data: {
            onboardingStatus: 'PENDING_ONBOARDING',
            inviteToken,
            inviteTokenExp,
          }
        });

        try {
          // Trigger the SMTP email since assets are now assigned!
          await this.mailService.sendInviteEmail(user.email, inviteToken!);
        } catch (err) {
          console.error(`[SMTP ERROR] Non-fatal update transition email failure: ${(err as Error).message}`);
        }
      } else if (oldStatus === 'PENDING_ONBOARDING' && options.sharedAssetIds.length === 0) {
        // Transition back if all assets are removed before onboarding starts
        await prisma.user.update({
          where: { id: partnerId },
          data: {
            onboardingStatus: 'PENDING_ASSETS'
          }
        });
      }
    }

    return this.getUserById(partnerId);
  }

  public async resendInvite(partnerId: string) {
    const user = await prisma.user.findUnique({
      where: { id: partnerId }
    });
    if (!user) throw new AppError('Partner not found', 404);
    if (user.role === 'ADMIN') throw new AppError('Cannot send invitation to admins', 400);
    if (user.onboardingStatus === 'APPROVED') {
      throw new AppError('Partner is already active/approved', 400);
    }

    let inviteToken = user.inviteToken;
    let inviteTokenExp = user.inviteTokenExp;

    // Generate token if missing or expired
    if (!inviteToken || !inviteTokenExp || inviteTokenExp < new Date()) {
      inviteToken = crypto.randomBytes(32).toString('hex');
      inviteTokenExp = new Date(Date.now() + 24 * 60 * 60 * 1000); // 24 hours
      await prisma.user.update({
        where: { id: partnerId },
        data: {
          inviteToken,
          inviteTokenExp,
          onboardingStatus: 'PENDING_ONBOARDING'
        }
      });
    } else if (user.onboardingStatus === 'PENDING_ASSETS') {
      // Force onboarding status to PENDING_ONBOARDING
      await prisma.user.update({
        where: { id: partnerId },
        data: {
          onboardingStatus: 'PENDING_ONBOARDING'
        }
      });
    }

    try {
      await this.mailService.sendInviteEmail(user.email, inviteToken);
    } catch (err) {
      throw new AppError(`Failed to send invitation email: ${(err as Error).message}`, 500);
    }
    return { success: true };
  }

  public async validateInvite(token: string) {
    const user = await prisma.user.findUnique({ where: { inviteToken: token } });
    if (!user || !user.inviteTokenExp || user.inviteTokenExp < new Date()) {
      throw new AppError('Invalid or expired invite token', 400);
    }
    return { email: user.email };
  }

  public async acceptInvite(token: string, passwordString: string) {
    const user = await prisma.user.findUnique({ where: { inviteToken: token } });
    if (!user || !user.inviteTokenExp || user.inviteTokenExp < new Date()) {
      throw new AppError('Invalid or expired invite token', 400);
    }

    const passwordHash = await bcrypt.hash(passwordString, 10);
    const noAgreements = !user.assignedNdaId && !user.assignedMsaId;
    const newOnboardingStatus = noAgreements ? 'APPROVED' : user.onboardingStatus;

    const updatedUser = await prisma.user.update({
      where: { id: user.id },
      data: {
        passwordHash,
        inviteToken: null,
        inviteTokenExp: null,
        onboardingStatus: newOnboardingStatus,
      }
    });

    const secret = process.env.JWT_SECRET || 'secret';
    const accessToken = jwt.sign({ userId: updatedUser.id, role: updatedUser.role }, secret, { expiresIn: '15m' });
    const refreshToken = jwt.sign({ userId: updatedUser.id }, secret, { expiresIn: '7d' });

    const userWithOrg = await this.getUserById(updatedUser.id);
    return { user: userWithOrg, accessToken, refreshToken };
  }

  public async login(email: string, passwordString: string) {
    const user = await prisma.user.findUnique({ where: { email } });
    if (!user) {
      throw new AppError('Invalid credentials', 401);
    }
    
    const isMatch = await bcrypt.compare(passwordString, user.passwordHash);
    if (!isMatch) {
      throw new AppError('Invalid credentials', 401);
    }

    const secret = process.env.JWT_SECRET || 'secret';
    const accessToken = jwt.sign({ userId: user.id, role: user.role }, secret, { expiresIn: '15m' });
    const refreshToken = jwt.sign({ userId: user.id }, secret, { expiresIn: '7d' });

    const userWithOrg = await this.getUserById(user.id);
    return { user: userWithOrg, accessToken, refreshToken };
  }

  public async refresh(refreshToken: string) {
    const secret = process.env.JWT_SECRET || 'secret';
    try {
      const decoded = jwt.verify(refreshToken, secret) as any;
      const user = await prisma.user.findUnique({ where: { id: decoded.userId } });
      if (!user) throw new AppError('Invalid refresh token', 401);
      
      const newAccessToken = jwt.sign({ userId: user.id, role: user.role }, secret, { expiresIn: '15m' });
      const userWithOrg = await this.getUserById(user.id);
      return { accessToken: newAccessToken, user: userWithOrg };
    } catch(err) {
      throw new AppError('Invalid or expired refresh token', 401);
    }
  }

  public async listPartners() {
    const partners = await prisma.user.findMany({
      where: { role: 'PARTNER_USER' },
      select: {
        id: true,
        email: true,
        onboardingStatus: true,
        mfaEnabled: true,
        showEcosystemTab: true,
        createdAt: true,
        updatedAt: true,
        organizationId: true,
        partnerGroup: true,
        assignedNdaId: true,
        assignedMsaId: true,
        assignedNda: {
          select: { id: true, version: true }
        },
        assignedMsa: {
          select: { id: true, version: true }
        },
        inviteToken: true,
      },
      orderBy: { createdAt: 'desc' },
    });

    const partnersWithSharedAssets = await Promise.all(
      partners.map(async (partner) => {
        let sharedAssets: any[] = [];
        if (partner.organizationId) {
          sharedAssets = await prisma.sharedAsset.findMany({
            where: {
              organizationId: partner.organizationId,
              OR: [
                { userId: null },
                { userId: partner.id }
              ]
            },
            select: {
              assetId: true,
              asset: {
                select: {
                  id: true,
                  title: true,
                  type: true,
                  categoryId: true,
                  subcategory: true,
                  url: true
                }
              }
            }
          });
        }
        return {
          ...partner,
          sharedAssets
        };
      })
    );

    return partnersWithSharedAssets;
  }

  public async getUserById(id: string) {
    let user = await prisma.user.findUnique({
      where: { id },
      select: {
        id: true,
        email: true,
        role: true,
        mfaEnabled: true,
        organizationId: true,
        onboardingStatus: true,
        partnerGroup: true,
        website: true,
        sector: true,
        companySize: true,
        defaultTheme: true,
        companyName: true,
        createdAt: true,
        updatedAt: true,
        assignedNdaId: true,
        assignedMsaId: true,
        showEcosystemTab: true,
        organization: {
          select: {
            id: true,
            name: true,
            status: true,
          }
        }
      }
    });

    if (user && user.role === 'PARTNER_USER' && !user.organizationId) {
      const orgId = await this.getOrCreateOrganizationForEmail(user.email);
      if (orgId) {
        await prisma.user.update({
          where: { id },
          data: { organizationId: orgId }
        });
        // refetch to get populated organization relation
        user = await prisma.user.findUnique({
          where: { id },
          select: {
            id: true,
            email: true,
            role: true,
            mfaEnabled: true,
            organizationId: true,
            onboardingStatus: true,
            partnerGroup: true,
            website: true,
            sector: true,
            companySize: true,
            defaultTheme: true,
            companyName: true,
            createdAt: true,
            updatedAt: true,
            assignedNdaId: true,
            assignedMsaId: true,
            showEcosystemTab: true,
            organization: {
              select: {
                id: true,
                name: true,
                status: true,
              }
            }
          }
        });
      }
    }

    if (user && user.role === 'PARTNER_USER' && user.organizationId) {
      const sharedAssets = await prisma.sharedAsset.findMany({
        where: {
          organizationId: user.organizationId,
          OR: [
            { userId: null },
            { userId: user.id }
          ]
        },
        select: {
          assetId: true,
          asset: {
            select: {
              id: true,
              title: true,
              type: true,
              categoryId: true,
              subcategory: true,
              url: true
            }
          }
        }
      });
      return {
        ...user,
        sharedAssets
      };
    }

    return user;
  }

  public async updateProfile(userId: string, data: {
    password?: string;
    companyName?: string;
    website?: string;
    sector?: string;
    companySize?: string;
    defaultTheme?: string;
  }) {
    const user = await prisma.user.findUnique({
      where: { id: userId },
      include: { organization: true }
    });

    if (!user) throw new AppError('User not found', 404);

    const updateData: any = {};

    if (data.password) {
      updateData.passwordHash = await bcrypt.hash(data.password, 10);
    }
    if (data.website !== undefined) updateData.website = data.website;
    if (data.sector !== undefined) updateData.sector = data.sector;
    if (data.companySize !== undefined) updateData.companySize = data.companySize;
    if (data.defaultTheme !== undefined) updateData.defaultTheme = data.defaultTheme;
    if (data.companyName !== undefined) {
      updateData.companyName = data.companyName;
      if (user.organizationId) {
        await prisma.organization.update({
          where: { id: user.organizationId },
          data: { name: data.companyName }
        });
      }
    }

    const updated = await prisma.user.update({
      where: { id: userId },
      data: updateData,
      select: {
        id: true,
        email: true,
        role: true,
        mfaEnabled: true,
        organizationId: true,
        onboardingStatus: true,
        partnerGroup: true,
        website: true,
        sector: true,
        companySize: true,
        defaultTheme: true,
        companyName: true,
        createdAt: true,
        updatedAt: true,
        assignedNdaId: true,
        assignedMsaId: true,
        organization: {
          select: {
            id: true,
            name: true,
            status: true,
          }
        }
      }
    });

    return this.getUserById(userId);
  }
}
