import { prisma } from '@shieldai/db';
import { RateLimiterMemory, RateLimiterRes } from 'rate-limiter-flexible';
import { createCache } from 'cache-manager';
import { CacheableMemory } from 'cacheable';
import { v4 as uuidv4 } from 'uuid';
import type { PropertyRecord, CountyDeedRecord, DataSourceType } from './types';

export interface ATTOMPropertyResponse {
  data: {
    propertyId: string;
    parcelNumber: string;
    address: {
      streetAddress: string;
      city: string;
      state: string;
      zipCode: string;
      latitude: number;
      longitude: number;
    };
    owner: {
      ownerName1: string;
      ownerName2?: string;
      mailingAddress?: string;
    };
    assessment: {
      totalValue: number;
      landValue: number;
      improvementValue: number;
      taxYear: number;
      taxAmount: number;
    };
    propertyDetails: {
      propertyType: string;
      yearBuilt: number;
      squareFeet: number;
      bedrooms?: number;
      bathrooms?: number;
    };
    salesHistory: Array<{
      saleDate: string;
      salePrice: number;
      documentType: string;
    }>;
  };
}

export interface USPSStandardizedAddress {
  streetNumber: string;
  streetName: string;
  streetSuffix?: string;
  unitType?: string;
  unitValue?: string;
  city: string;
  state: string;
  zip5: string;
  zip4?: string;
  deliveryPointBarCode?: string;
  isDeliverable: boolean;
}

interface ScannerConfig {
  attomApiKey: string;
  uspsApiKey: string;
  countyScraperEnabled: boolean;
  cacheTTL: number;
  rateLimitPoints: number;
  rateLimitDuration: number;
  retryAttempts: number;
  retryDelayMs: number;
}

const DEFAULT_CONFIG: ScannerConfig = {
  attomApiKey: process.env.ATTOM_API_KEY || '',
  uspsApiKey: process.env.USPS_API_KEY || '',
  countyScraperEnabled: process.env.COUNTY_SCRAPER_ENABLED === 'true',
  cacheTTL: 3600,
  rateLimitPoints: 100,
  rateLimitDuration: 60,
  retryAttempts: 3,
  retryDelayMs: 1000,
};

export class PropertyScannerService {
  private config: ScannerConfig;
  private cache: ReturnType<typeof createCache>;
  private attomRateLimiter: RateLimiterMemory;
  private countyRateLimiter: RateLimiterMemory;

  constructor(config?: Partial<ScannerConfig>) {
    this.config = { ...DEFAULT_CONFIG, ...config };
    const memoryStore = new CacheableMemory({
      ttl: this.config.cacheTTL * 1000,
      lruSize: 10000,
    });
    this.cache = createCache({
      stores: [memoryStore as any],
    });
    this.attomRateLimiter = new RateLimiterMemory({
      points: this.config.rateLimitPoints,
      duration: this.config.rateLimitDuration,
    });
    this.countyRateLimiter = new RateLimiterMemory({
      points: this.config.rateLimitPoints,
      duration: this.config.rateLimitDuration,
    });
  }

  private async withRetry<T>(
    fn: () => Promise<T>,
    operation: string,
    attempts = this.config.retryAttempts
  ): Promise<T> {
    for (let i = 0; i < attempts; i++) {
      try {
        return await fn();
      } catch (error) {
        const isLastAttempt = i === attempts - 1;
        if (isLastAttempt) {
          throw new Error(`Failed ${operation} after ${attempts} attempts: ${error}`);
        }
        await new Promise(resolve => setTimeout(resolve, this.config.retryDelayMs * Math.pow(2, i)));
      }
    }
    throw new Error(`Failed ${operation}`);
  }

  private async checkRateLimiter(rateLimiter: RateLimiterMemory, key: string): Promise<void> {
    try {
      await rateLimiter.consume(key);
    } catch (rejRes) {
      const res = rejRes as RateLimiterRes;
      const retryAfter = res.msBeforeNext / 1000;
      throw new Error(`Rate limit exceeded. Retry after ${retryAfter} seconds`);
    }
  }

  async fetchByAddress(address: string, county?: string): Promise<PropertyRecord | null> {
    const cacheKey = `property:address:${address}`;
    
    try {
      const cached = await this.cache.get<PropertyRecord>(cacheKey);
      if (cached) {
        return cached;
      }
    } catch (error) {
      console.warn('Cache read error:', error);
    }

    const record = await this.withRetry(
      async () => {
        await this.checkRateLimiter(this.attomRateLimiter, 'attom');
        
        const attomResult = await this.fetchFromATTOM({ address });
        if (!attomResult) {
          return null;
        }

        const deedHistory = this.config.countyScraperEnabled && county
          ? await this.fetchCountyDeeds(attomResult.parcelNumber, county)
          : [];

        const liens = this.config.countyScraperEnabled && county
          ? await this.fetchCountyLiens(attomResult.parcelNumber, county)
          : [];

        return {
          ...attomResult,
          deedHistory: deedHistory.length > 0 ? deedHistory : undefined,
          liens: liens.length > 0 ? liens : undefined,
          dataSource: (deedHistory.length > 0 || liens.length > 0 ? 'combined' : 'attom') as DataSourceType,
          lastUpdated: new Date().toISOString(),
        };
      },
      'fetch property by address'
    );

    if (record) {
      try {
        await this.cache.set(cacheKey, record, this.config.cacheTTL);
      } catch (error) {
        console.warn('Cache write error:', error);
      }
    }

    return record;
  }

  async fetchByParcel(parcelNumber: string, county: string): Promise<PropertyRecord | null> {
    const cacheKey = `property:parcel:${parcelNumber}:${county}`;
    
    try {
      const cached = await this.cache.get<PropertyRecord>(cacheKey);
      if (cached) {
        return cached;
      }
    } catch (error) {
      console.warn('Cache read error:', error);
    }

    const record = await this.withRetry(
      async () => {
        await this.checkRateLimiter(this.attomRateLimiter, 'attom');
        
        const attomResult = await this.fetchFromATTOM({ parcelNumber, county });
        if (!attomResult) {
          return null;
        }

        const deedHistory = this.config.countyScraperEnabled
          ? await this.fetchCountyDeeds(parcelNumber, county)
          : [];

        const liens = this.config.countyScraperEnabled
          ? await this.fetchCountyLiens(parcelNumber, county)
          : [];

        return {
          ...attomResult,
          deedHistory: deedHistory.length > 0 ? deedHistory : undefined,
          liens: liens.length > 0 ? liens : undefined,
          dataSource: (deedHistory.length > 0 || liens.length > 0 ? 'combined' : 'attom') as DataSourceType,
          lastUpdated: new Date().toISOString(),
        };
      },
      'fetch property by parcel'
    );

    if (record) {
      try {
        await this.cache.set(cacheKey, record, this.config.cacheTTL);
      } catch (error) {
        console.warn('Cache write error:', error);
      }
    }

    return record;
  }

  private async fetchFromATTOM(
    query: { address?: string; parcelNumber?: string; county?: string }
  ): Promise<Omit<PropertyRecord, 'deedHistory' | 'liens' | 'dataSource' | 'lastUpdated'> | null> {
    try {
      const url = new URL('https://api.attomdata.com/propertyapi/v4.0.0/property/get');
      
      if (query.address) {
        url.searchParams.append('address', query.address);
      } else if (query.parcelNumber && query.county) {
        url.searchParams.append('parcelnumber', query.parcelNumber);
        url.searchParams.append('county', query.county);
      } else {
        throw new Error('Either address or parcelNumber+county must be provided');
      }

      const response = await fetch(url.toString(), {
        headers: {
          'Authorization': `Bearer ${this.config.attomApiKey}`,
          'Content-Type': 'application/json',
        },
      });

      if (response.status === 404) {
        return null;
      }

      if (!response.ok) {
        throw new Error(`ATTOM API error: ${response.status} ${response.statusText}`);
      }

      const data = await response.json() as ATTOMPropertyResponse;
      
      return {
        id: uuidv4(),
        address: {
          streetAddress: data.data.address?.streetAddress ?? 'Unknown',
          city: data.data.address?.city ?? 'Unknown',
          state: data.data.address?.state ?? 'Unknown',
          zipCode: data.data.address?.zipCode ?? 'Unknown',
          latitude: data.data.address?.latitude,
          longitude: data.data.address?.longitude,
        },
        parcelNumber: data.data.parcelNumber ?? 'Unknown',
        ownerName: data.data.owner?.ownerName1 ?? 'Unknown',
        assessment: data.data.assessment
          ? {
              totalValue: data.data.assessment.totalValue,
              landValue: data.data.assessment.landValue,
              improvementValue: data.data.assessment.improvementValue,
              taxYear: data.data.assessment.taxYear,
              taxAmount: data.data.assessment.taxAmount,
            }
          : undefined,
        propertyDetails: data.data.propertyDetails
          ? {
              propertyType: data.data.propertyDetails.propertyType,
              yearBuilt: data.data.propertyDetails.yearBuilt,
              squareFeet: data.data.propertyDetails.squareFeet,
            }
          : undefined,
      };
    } catch (error) {
      console.error('ATTOM API fetch error:', error);
      throw error;
    }
  }

  private async fetchCountyDeeds(parcelNumber: string, county: string): Promise<CountyDeedRecord[]> {
    try {
      await this.checkRateLimiter(this.countyRateLimiter, `county:${county}`);
      
      const countyRecords = await prisma.countyDeedRecord.findMany({
        where: {
          parcelNumber,
          county,
          documentType: {
            in: ['DEED', 'QUITCLAIM DEED', 'WARRANTY DEED', 'SPECIAL WARRANTY DEED'],
          },
        },
        orderBy: { recordingDate: 'desc' },
        take: 10,
      });

      return countyRecords.map((record: {
        documentId: string;
        recordingDate: Date;
        documentType: string;
        grantorName: string;
        granteeName: string;
        propertyAddress: string;
        parcelNumber: string;
        considerationAmount: number | null;
        lienAmount: number | null;
      }) => ({
        documentId: record.documentId,
        recordingDate: record.recordingDate.toISOString(),
        documentType: record.documentType,
        grantorName: record.grantorName,
        granteeName: record.granteeName,
        propertyAddress: record.propertyAddress,
        parcelNumber: record.parcelNumber,
        considerationAmount: record.considerationAmount ?? undefined,
      }));
    } catch (error) {
      console.error(`County deeds fetch error for ${county}:`, error);
      return [];
    }
  }

  private async fetchCountyLiens(parcelNumber: string, county: string): Promise<CountyDeedRecord[]> {
    try {
      await this.checkRateLimiter(this.countyRateLimiter, `county:${county}`);
      
      const lienRecords = await prisma.countyDeedRecord.findMany({
        where: {
          parcelNumber,
          county,
          documentType: {
            in: ['LIEN', 'MECHANIC LIEN', 'TAX LIEN', 'JUDGMENT LIEN', 'MORTGAGE'],
          },
        },
        orderBy: { recordingDate: 'desc' },
        take: 20,
      });

      return lienRecords.map((record: {
        documentId: string;
        recordingDate: Date;
        documentType: string;
        grantorName: string;
        granteeName: string;
        propertyAddress: string;
        parcelNumber: string;
        considerationAmount: number | null;
        lienAmount: number | null;
      }) => ({
        documentId: record.documentId,
        recordingDate: record.recordingDate.toISOString(),
        documentType: record.documentType,
        grantorName: record.grantorName,
        granteeName: record.granteeName,
        propertyAddress: record.propertyAddress,
        parcelNumber: record.parcelNumber,
        lienAmount: record.lienAmount ?? undefined,
        lienType: record.documentType,
      }));
    } catch (error) {
      console.error(`County liens fetch error for ${county}:`, error);
      return [];
    }
  }

  async standardizeAddress(address: string): Promise<USPSStandardizedAddress | null> {
    const cacheKey = `usps:address:${address}`;
    
    try {
      const cached = await this.cache.get<USPSStandardizedAddress>(cacheKey);
      if (cached) {
        return cached;
      }
    } catch (error) {
      console.warn('Cache read error:', error);
    }

    const standardized = await this.withRetry(
      async () => {
        await this.checkRateLimiter(this.attomRateLimiter, 'usps');
        
        const url = new URL('https://api.usps.com/api/address/standardize');
        url.searchParams.append('address', address);
        url.searchParams.append('api-key', this.config.uspsApiKey);

        const response = await fetch(url.toString());

        if (!response.ok) {
          if (response.status === 404) {
            return null;
          }
          throw new Error(`USPS API error: ${response.status} ${response.statusText}`);
        }

        const data = await response.json() as any;
        
        const result: USPSStandardizedAddress = {
          streetNumber: data.streetNumber,
          streetName: data.streetName,
          streetSuffix: data.streetSuffix,
          unitType: data.unitType,
          unitValue: data.unitValue,
          city: data.city,
          state: data.state,
          zip5: data.zip5,
          zip4: data.zip4,
          deliveryPointBarCode: data.deliveryPointBarCode,
          isDeliverable: data.isDeliverable,
        };

        return result;
      },
      'standardize address'
    );

    if (standardized) {
      try {
        await this.cache.set(cacheKey, standardized, this.config.cacheTTL);
      } catch (error) {
        console.warn('Cache write error:', error);
      }
    }

    return standardized;
  }

  async batchFetchProperties(
    requests: Array<{ address?: string; parcelNumber?: string; county?: string }>
  ): Promise<(PropertyRecord | null)[]> {
    const results: (PropertyRecord | null)[] = [];
    
    for (const request of requests) {
      try {
        if (request.address) {
          const result = await this.fetchByAddress(request.address, request.county);
          results.push(result);
        } else if (request.parcelNumber && request.county) {
          const result = await this.fetchByParcel(request.parcelNumber, request.county);
          results.push(result);
        } else {
          results.push(null);
        }
        
        await new Promise(resolve => setTimeout(resolve, 100));
      } catch (error) {
        console.error('Batch fetch error:', error);
        results.push(null);
      }
    }
    
    return results;
  }

  async invalidateCache(address?: string, parcelNumber?: string, county?: string): Promise<void> {
    if (address) {
      await this.cache.del(`property:address:${address}`);
    }
    if (parcelNumber && county) {
      await this.cache.del(`property:parcel:${parcelNumber}:${county}`);
    }
  }

  async getCacheStats(): Promise<{ hits: number; misses: number; size: number }> {
    try {
      const stats = await this.cache.get<{ hits: number; misses: number; size: number }>('cache:stats') || {
        hits: 0,
        misses: 0,
        size: 0,
      };
      return stats;
    } catch (error) {
      console.warn('Cache stats error:', error);
      return { hits: 0, misses: 0, size: 0 };
    }
  }
}

export const propertyScannerService = new PropertyScannerService();
